#define _FILE_OFFSET_BITS 64

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>

#include "Util.h"

#define NUM_SEQUENTIAL_READS 10
#define BLOCK_SIZE 4096

typedef enum {
	SEQUENTIAL, RANDOMOFFSET_SEQUENTIAL, RANDOM
} readType_t;

struct workLoadParams {
	int option;
	int fileDescriptor;
	long fileSize;
	long numReads;
};

void doSequentialReads(int fileDescriptor, long numReads, long fileSize);
void doRandOffsetAndSequentialReads(int fileDescriptor, long numReads, long fileSize);
void doRandomReads(int fileDescriptor, long numReads, long fileSize);

void runWorkload(void* ptr);
struct workLoadParams* constructWorkLoadParams(int option, int fileDescriptor, long fileSize, long numReads);

long getRandomNumber();
void readBlocks(int fileDescriptor, long offset);

int main(int argc, char *argv[]) {
	long fileSize, numReads;
	int option = 0, fileDescriptor = 0;
	char *fileName = NULL;
	readType_t readType;
    struct workLoadParams* params;

    validateAndFillInputArgumentsForFileSystemWorkload(argc, argv, &fileName,
			"FileName", &numReads, "NumReads", &option, "Option");
	readType = option;
	srand(time(NULL ));

	fileDescriptor = open(fileName, O_RDONLY);
    if(fileDescriptor == -1) {
    	printf("Unable to open file %s\n", fileName);
    	perror("Checking the error");
    	exit(-1);
    }

    printf("Filename :%s\n", fileName);

    fileSize = getFileSizeFromFD(fileDescriptor);
    printf("Filesize :%lu\n", fileSize);

    params = constructWorkLoadParams(option, fileDescriptor, fileSize, numReads);

    runWorkload((void*)params);

	return EXIT_SUCCESS;
}

void runWorkload(void* ptr) {
    double timeForReads;
    struct timespec startTime, endTime;
    struct workLoadParams* param = (struct workLoadParams*) ptr;

    readType_t readType = param->option;
    int fileDescriptor = param->fileDescriptor;
    long fileSize = param->fileSize;
    long numReads = param->numReads;

	clock_gettime(CLOCK_REALTIME, &startTime);
	switch (readType) {
	case SEQUENTIAL:
		doSequentialReads(fileDescriptor, numReads, fileSize);
		break;
	case RANDOMOFFSET_SEQUENTIAL:
		doRandOffsetAndSequentialReads(fileDescriptor, numReads, fileSize);
		break;
	case RANDOM:
		doRandomReads(fileDescriptor, numReads, fileSize);
		break;
	default:
		printf("ERROR: ReadType is %d\n", readType);
		exit(-1);
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeForReads = getDiffInNanoSeconds(startTime, endTime);
	timeForReads /= 10e6;

	//printf("%lu %f\n", numReads,  (double) timeForReads / (double) numReads);
	printf("%lu %f\n", numReads,  (double) timeForReads);
	return;
}

struct workLoadParams* constructWorkLoadParams(int option, int fileDescriptor, long fileSize, long numReads) {
	struct workLoadParams *param = (struct workLoadParams*) malloc(sizeof(struct workLoadParams));
	param->option = option;
	param->fileDescriptor = fileDescriptor;
	param->fileSize = fileSize;
	param->numReads = numReads;
	printf("Option: %d \tFileDescriptor: %d \tFileSize :%lu \t numReads :%lu\n", option, fileDescriptor, fileSize, numReads);
	return param;
}

void doSequentialReads(int fileDescriptor, long numReads, long fileSize) {
	unsigned long offset = 0;
    int counter = 0;
	for(counter = 0; counter < numReads ; counter++ ) {
		//offset = (unsigned long) getRandomNumber();
		readBlocks(fileDescriptor, offset);
		offset += BLOCK_SIZE;
	}
}

void doRandOffsetAndSequentialReads(int fileDescriptor, long numReads, long fileSize) {
	unsigned long offset = 0;
    int randCounter = 0, numRandOffsets = numReads / NUM_SEQUENTIAL_READS, seqCounter;
	for(randCounter = 0; randCounter < numRandOffsets ; randCounter++ ) {
		offset = (unsigned long) getRandomNumber();
		for(seqCounter = 0; seqCounter < NUM_SEQUENTIAL_READS; seqCounter++) {
			readBlocks(fileDescriptor, offset);
			offset += BLOCK_SIZE;
		}
	}
}

void doRandomReads(int fileDescriptor, long numReads, long fileSize) {
	unsigned long offset = 0;
    int counter = 0;
	for(counter = 0; counter < numReads ; counter++ ) {
		offset = (unsigned long) getRandomNumber();
		readBlocks(fileDescriptor, offset);
	}
}

void readBlocks(int fileDescriptor, long offset) {
	char buffer[BLOCK_SIZE];
	int readResult, lseekResult;
	lseekResult = lseek(fileDescriptor, offset, SEEK_SET);
	if(lseekResult == -1) {
		printf("Unable to seek to offset %lu\n", offset);
		exit(-1);
	}
	readResult = read(fileDescriptor, buffer, BLOCK_SIZE);
    if(readResult == -1) {
    	printf("Unable to read block from location %lu of file %d\n", offset, fileDescriptor);
    	exit(-1);
    }
    //printf("Read a block from offset %lu and size %d\n", offset, readResult);
}

long getRandomNumber() {
	uint64_t random =
	  (((uint64_t) rand() <<  0) & 0x00000000FFFFFFFFull) |
	  (((uint64_t) rand() << 32) & 0xFFFFFFFF00000000ull);
	return (long) random;
}
